/*
 *   Copyright 2021 zzh
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.aduib.boot.common.buffer;

import com.aduib.boot.common.lang.ObjectPool;
import com.aduib.boot.common.util.SystemUtils;

import java.util.Objects;

/**
 * @description: PoolBuffer
 * @author: zzh
 * @date: 2021/12/3 14:05
 */
public final class PooledBuffer extends Buffer {

  private static final ObjectPool<PooledBuffer> RECYCLER =
      ObjectPool.newPool(handle -> new PooledBuffer(handle, 0));
  private final ObjectPool.Handle<PooledBuffer> recyclerHandle;
  protected BufferChunk chunk;
  protected long handle;
  protected Buffer memory;
  protected int offset;
  protected int length;
  int maxLength;
  PoolThreadCache cache;
  /** 当是直接内存时，buffer的内存地址 */
  private long memoryAddress = -1L;
  private BufferPool bufferPool;

  protected PooledBuffer(
      ObjectPool.Handle<? extends PooledBuffer> recyclerHandle, int maxCapacity) {
    super(maxCapacity);
    this.recyclerHandle = (ObjectPool.Handle<PooledBuffer>) recyclerHandle;
  }

  static PooledBuffer newInstance(int maxCapacity) {
    PooledBuffer buf = RECYCLER.get();
    buf.reuse(maxCapacity);
    return buf;
  }

  void init(
      BufferChunk chunk,
      Buffer nioBuffer,
      long handle,
      int offset,
      int length,
      int maxLength,
      PoolThreadCache cache) {
    init0(chunk, nioBuffer, handle, offset, length, maxLength, cache);
  }

  void initUnpooled(BufferChunk chunk, int length) {
    init0(chunk, null, 0, 0, length, length, null);
  }

  private void init0(
      BufferChunk chunk,
      Buffer nioBuffer,
      long handle,
      int offset,
      int length,
      int maxLength,
      PoolThreadCache cache) {
    assert handle >= 0;
    assert chunk != null;

    this.chunk = chunk;
    memory = chunk.memory;
    bufferPool = chunk.bufferPool;
    this.cache = cache;
    this.handle = handle;
    this.offset = offset;
    this.length = length;
    this.maxLength = maxLength;
    capacity(nioBuffer.getByteBuffer());
    initMemoryAddress();
  }

  @Override
  protected final void deallocate() {
    if (handle >= 0) {
      final long handle = this.handle;
      this.handle = -1;
      memory = null;
      ((BufferPoolArena) bufferPool).free(cache, this, handle);
      chunk = null;
      recycle();
    }
  }

  private void recycle() {
    recyclerHandle.recycle(this);
  }

  protected final int idx(int index) {
    return offset + index;
  }

  /** Method must be called before reuse this {@link PooledBufferAllocator} */
  final void reuse(int maxCapacity) {
    newCapacity(maxCapacity);
    resetRefCnt();
  }

  private void initMemoryAddress() {
    memoryAddress = SystemUtils.directBufferAddress(getByteBuffer()) + offset;
  }

  @Override
  public String toString() {
    return super.toString();
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    if (!super.equals(o)) return false;
    PooledBuffer that = (PooledBuffer) o;
    return length == that.length
        && maxLength == that.maxLength
        && memoryAddress == that.memoryAddress;
  }

  @Override
  public int hashCode() {
    return Objects.hash(super.hashCode(), length, maxLength, memoryAddress);
  }

  @Override
  public void clear() {
    release(refCnt());
    super.clear();
  }

  @Override
  public Buffer slice() {
    retain();
    return super.slice();
  }

  @Override
  public Buffer duplicate() {
    retain();
    return super.duplicate();
  }

  @Override
  public Buffer asReadOnlyBuffer() {
    retain();
    return super.asReadOnlyBuffer();
  }
}
